namespace WP7Contrib.Services.BingMaps.Model
{
    using System;
    using System.Linq;
    using Common.Extensions;
    using Collections;
    using Common;

    public class RouteSearchCriterion : BaseModel, ICriterion<RouteSearchCriterion>,
                                                   IEquatable<RouteSearchCriterion>,
                                                   ICloneable<RouteSearchCriterion>, 
                                                   IRouteSearchCriterion,
                                                   IRouteSearchMajorRoadCriterion
    {
        private Avoid avoid;
        private RouteDestination destination;
        private DistanceUnit distanceUnit;
        private DateTime? dateTime;
        private RouteExclude exclude;
        private int? heading;
        private int? maxSolutions;
        private Optimize? optimize;
        private RoutePathOutput pathOutput;
        private string pointOfInterest;
        private ModeOfTravel? travelMode;
        private TimeType timeType;
        
        private ObservableCollection<WayPoint> wayPoints;

        public RouteSearchCriterion()
        {
            this.Reset();
        }

        public Avoid Avoid
        {
            get
            {
                return this.avoid;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.avoid, value, () => this.Avoid);
            }
        }

        public bool HasAvoid
        {
            get { return this.avoid != null; }
        }
        
        public ObservableCollection<WayPoint> WayPoints
        {
            get
            {
                return this.wayPoints;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.wayPoints, value, () => this.WayPoints);
            }
        }

        public bool HasWayPoints
        {
            get { return this.wayPoints != null && this.wayPoints.Count != 0; }
        }
        
        public int? Heading
        {
            get
            {
                return this.heading;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.heading, value, () => this.Heading);
            }
        }

        public Optimize? Optimize
        {
            get
            {
                return this.optimize;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.optimize, value, () => this.Optimize);
            }
        }

        public RouteDestination Destination
        {
            get
            {
                return this.destination;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.destination, value, () => this.Destination);
            }
        }

        public bool HasDestination
        {
            get { return this.destination != null; }
        }

        public DistanceUnit DistanceUnit
        {
            get
            {
                return this.distanceUnit;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.distanceUnit, value, () => this.DistanceUnit);
            }
        }

        public DateTime? DateTime
        {
            get
            {
                return this.dateTime;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.dateTime, value, () => this.DateTime);
            }
        }

        public RouteExclude Exclude
        {
            get
            {
                return this.exclude;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.exclude, value, () => this.Exclude);
            }
        }

        public int? MaxSolutions
        {
            get
            {
                return this.maxSolutions;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.maxSolutions, value, () => this.MaxSolutions);
            }
        }

        public RoutePathOutput PathOutput
        {
            get
            {
                return this.pathOutput;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.pathOutput, value, () => this.PathOutput);
            }
        }

        public string PointOfInterest
        {
            get
            {
                return this.pointOfInterest;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.pointOfInterest, value, () => this.PointOfInterest);
            }
        }

        public TimeType TimeType
        {
            get
            {
                return this.timeType;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.timeType, value, () => this.TimeType);
            }
        }

        public ModeOfTravel? TravelMode
        {
            get
            {
                return this.travelMode;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.travelMode, value, () => this.TravelMode);
            }
        }

        public RouteSearchCriterion Reset()
        {
            this.dateTime = null;
            this.heading = null;
            this.pointOfInterest = null;

            this.avoid = new Avoid();
            this.optimize = null;
            this.destination = new RouteDestination();
            this.distanceUnit = DistanceUnit.Kilometer;
            this.exclude = RouteExclude.Routes;
            this.maxSolutions = 1;
            this.pathOutput = RoutePathOutput.Points;
            this.travelMode = ModeOfTravel.Driving;
            this.timeType = TimeType.Departure;
            this.wayPoints = new ObservableCollection<WayPoint>();

            return this;
        }

        public static bool operator ==(RouteSearchCriterion rsc1, RouteSearchCriterion rsc2)
        {
            return Equals(rsc1, rsc2);
        }

        public static bool operator !=(RouteSearchCriterion rsc1, RouteSearchCriterion rsc2)
        {
            return !Equals(rsc1, rsc2);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
            {
                return false;
            }

            return obj is RouteSearchCriterion && this.Equals((RouteSearchCriterion)obj);
        }

        public bool Equals(RouteSearchCriterion other)
        {
            if (ReferenceEquals(null, other))
            {
                return false;
            }

            if (this.Avoid != other.Avoid)
            {
                return false;
            }

            if (this.Destination != other.Destination)
            {
                return false;
            }

            if (this.DistanceUnit != other.DistanceUnit)
            {
                return false;
            }

            if (this.Exclude != other.Exclude)
            {
                return false;
            }

            if (this.Optimize != other.Optimize)
            {
                return false;
            }

            if (this.PathOutput != other.PathOutput)
            {
                return false;
            }

            if (this.DateTime != other.DateTime)
            {
                return false;
            }

            if (this.Heading != other.Heading)
            {
                return false;
            }

            if (this.MaxSolutions != other.MaxSolutions)
            {
                return false;
            }

            if (this.TravelMode != other.TravelMode)
            {
                return false;
            }

            if (!this.WayPoints.SequenceEqual(other.WayPoints))
            {
                return false;
            }

            return true;
        }

        public override int GetHashCode()
        {
            return this.CombineHashCodes(this.Avoid, this.Destination, this.DistanceUnit, this.Exclude, this.Optimize, this.PathOutput,
                                         this.DateTime, this.Heading, this.MaxSolutions, this.TravelMode, this.WayPoints.ToArray());
        }

        IRouteSearchCriterion ICloneable<IRouteSearchCriterion>.ShallowClone()
        {
            return this.ShallowClone();
        }

        IRouteSearchMajorRoadCriterion ICloneable<IRouteSearchMajorRoadCriterion>.ShallowClone()
        {
            return this.ShallowClone();
        }

        public RouteSearchCriterion ShallowClone()
        {
            var criterion = new RouteSearchCriterion
            {
                Avoid = this.Avoid,
                DistanceUnit = this.DistanceUnit,
                Destination = this.Destination,
                Exclude = this.Exclude,
                Optimize = this.Optimize,
                PathOutput = this.PathOutput,
                DateTime = this.DateTime,
                Heading = this.Heading,
                MaxSolutions = this.MaxSolutions,
                TravelMode = this.TravelMode,
                WayPoints = this.WayPoints
            };

            return criterion;
        }

        IRouteSearchCriterion ICloneable<IRouteSearchCriterion>.DeepClone()
        {
            return this.DeepClone();
        }

        IRouteSearchMajorRoadCriterion ICloneable<IRouteSearchMajorRoadCriterion>.DeepClone()
        {
            return this.DeepClone();
        }

        public RouteSearchCriterion DeepClone()
        {
            var criterion = new RouteSearchCriterion
            {
                DistanceUnit = this.DistanceUnit,
                Exclude = this.Exclude,
                Optimize = this.Optimize,
                PathOutput = this.PathOutput,
                DateTime = this.DateTime,
                Heading = this.Heading,
                MaxSolutions = this.MaxSolutions,
                TravelMode = this.TravelMode
            };

            if (this.HasAvoid)
                criterion.Avoid = this.Avoid.DeepClone();

            if (this.HasDestination)
                criterion.Destination = this.Destination.DeepClone();

            if (this.HasWayPoints)
            {
                var tmp = new ObservableCollection<WayPoint>();
                this.WayPoints.ForEach(w => tmp.Add(w.DeepClone()));
                criterion.WayPoints = tmp;
            }

            return criterion;
       }
    }
}